package com.carey.blog.action.file;

import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.Date;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import org.json.JSONObject;

import com.carey.blog.framework.latke.Keys;
import com.carey.blog.framework.latke.action.util.PageCaches;
import com.carey.blog.framework.latke.repository.Transaction;
import com.carey.blog.framework.latke.util.Ids;
import com.carey.blog.framework.latke.util.Locales;
import com.carey.blog.model.ErrorPage;
import com.carey.blog.model.File;
import com.carey.blog.model.Preference;
import com.carey.blog.repository.FileRepository;
import com.carey.blog.repository.impl.FileGAERepository;
import com.carey.blog.util.Preferences;
import com.carey.blog.util.TimeZones;
import com.google.appengine.api.datastore.Blob;

/**
 * File access via <a href=
 * "http://code.google.com/appengine/docs/java/javadoc/com/google/appengine/api/datastore/package-summary.html"
 * > Google Data Store Low-level API</a>.
 * 
 */
public final class DataStoreFileAccessServlet extends HttpServlet {

	/**
	 * Logger.
	 */
	private static final Logger LOGGER = Logger
			.getLogger(DataStoreFileAccessServlet.class.getName());
	/**
	 * Default serial version uid.
	 */
	private static final long serialVersionUID = 1L;
	/**
	 * File repository.
	 */
	private FileRepository fileRepository = FileGAERepository.getInstance();
	/**
	 * Maximum entity size limited by data store.
	 */
	private static final long MAX_SIZE = 1024 * 1024;
	/**
	 * Preference utilities.
	 */
	private Preferences preferenceUtils = Preferences.getInstance();
	/**
	 * Time zone utilities.
	 */
	private TimeZones timeZoneUtils = TimeZones.getInstance();

	@Override
	protected void doPost(final HttpServletRequest request,
			final HttpServletResponse response) throws ServletException,
			IOException {
		request.setCharacterEncoding("UTF-8");
		final ServletFileUpload upload = new ServletFileUpload();
		FileItemIterator iterator = null;

		try {
			iterator = upload.getItemIterator(request);

			while (iterator.hasNext()) {
				final FileItemStream item = iterator.next();
				final InputStream stream = item.openStream();

				final JSONObject preference = preferenceUtils.getPreference();
				if (null == preference) {
					response.sendError(HttpServletResponse.SC_NOT_FOUND);
					return;
				}

				final String localeString = preference
						.getString(Preference.LOCALE_STRING);
				final Locale locale = new Locale(
						Locales.getLanguage(localeString),
						Locales.getCountry(localeString));
				if (!item.isFormField()) {
					final ResourceBundle lang = ResourceBundle.getBundle(
							Keys.LANGUAGE, locale);
					// XXX: check size before streaming
					final byte[] contentBytes = IOUtils.toByteArray(stream);
					if (contentBytes.length > MAX_SIZE) {
						final String fail = lang.getString("uploadFailLabel");
						final String cause = lang
								.getString("exceedMaxUploadSizeLabel");
						sendError(request, response,
								HttpServletResponse.SC_BAD_REQUEST, fail, cause);
						return;
					}

					if (0 == contentBytes.length) {
						final String fail = lang.getString("uploadFailLabel");
						final String cause = lang.getString("fileEmptyLabel");
						sendError(request, response,
								HttpServletResponse.SC_BAD_REQUEST, fail, cause);
						return;
					}

					final Blob blob = new Blob(contentBytes);
					final JSONObject file = new JSONObject();
					final String id = Ids.genTimeMillisId();
					file.put(Keys.OBJECT_ID, id);

					file.put(File.FILE_CONTENT_TYPE, item.getContentType());
					file.put(File.FILE_CONTENT, blob);

					file.put(File.FILE_DOWNLOAD_COUNT, 0);
					final String timeZoneId = preference
							.getString(Preference.TIME_ZONE_ID);
					final Date createDate = timeZoneUtils.getTime(timeZoneId);
					file.put(File.FILE_UPLOAD_DATE, createDate);
					final String fileName = item.getName();
					file.put(File.FILE_NAME, fileName);
					final long fileSize = contentBytes.length;
					file.put(File.FILE_SIZE, fileSize);
					final String downloadURL = "/datastore-file-access.do?oId="
							+ id;
					file.put(File.FILE_DOWNLOAD_URL, downloadURL);

					fileRepository.add(file);

					PageCaches.removeAll(); // XXX: use repository cache instead
				}
			}

			response.sendRedirect("/admin-index.do");
		} catch (final Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new ServletException("File upload error: " + e.getMessage());
		}
	}

	@Override
	protected void doGet(final HttpServletRequest request,
			final HttpServletResponse response) throws ServletException,
			IOException {
		request.setCharacterEncoding("UTF-8");
		response.setCharacterEncoding("UTF-8");

		final String id = request.getParameter(Keys.OBJECT_ID);
		final Transaction transaction = fileRepository.beginTransaction();
		try {
			final JSONObject file = fileRepository.get(id);
			if (null == file) {
				response.sendError(HttpServletResponse.SC_NOT_FOUND);
				return;
			}

			final Blob content = (Blob) file.get(File.FILE_CONTENT);
			final String name = file.getString(File.FILE_NAME);
			response.addHeader("Content-Disposition", "attachment; filename="
					+ URLEncoder.encode(name, "UTF-8"));
			response.setContentType(file.getString(File.FILE_CONTENT_TYPE));
			response.getOutputStream().write(content.getBytes());
			response.getOutputStream().close();

			final int downloadCnt = file.getInt(File.FILE_DOWNLOAD_COUNT);
			file.put(File.FILE_DOWNLOAD_COUNT, downloadCnt + 1);
			fileRepository.update(id, file);
			transaction.commit();
		} catch (final Exception e) {
			if (transaction.isActive()) {
				transaction.rollback();
			}
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new ServletException("File download error: " + e.getMessage());
		}
	}

	/**
	 * Sends error via
	 * {@linkplain HttpServletResponse#sendError(int, java.lang.String)} with
	 * the specified error URI and cause.
	 * 
	 * @param request
	 *            the specified http servlet request
	 * @param response
	 *            the specified http servlet response
	 * @param errorCode
	 *            the specified error code
	 * @param errorURI
	 *            the specified error URI
	 * @param cause
	 *            the specified cause
	 * @throws IOException
	 *             io exception
	 */
	private void sendError(final HttpServletRequest request,
			final HttpServletResponse response, final int errorCode,
			final String errorURI, final String cause) throws IOException {
		request.setAttribute(ErrorPage.ERROR_PAGE_REQUEST_URI,
				request.getRequestURI());
		request.setAttribute(ErrorPage.ERROR_PAGE_CAUSE, errorURI + cause);
		response.sendError(errorCode);
	}
}
